Sources/XCMetricsClient/Mobius/Effect Handlers/DumpParser/JSONMetricsParserFactory.swift (99 lines of code) (raw):

// Copyright (c) 2020 Spotify AB. // // Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. import Foundation import XCLogParser enum MetricsParserError: Error { case invalidFormat case missingKey(String) } struct BuildInfo { let step: BuildStep let projectName: String? let userID: String? } struct NoticeInfo { let notice: Notice let parentIdentifier: String } protocol MetricsParserFactory { func buildBuildStepParser(with: BuildStepType) -> (Data) throws -> BuildInfo func buildNoticeParser(defaultType: NoticeType) -> (Data) throws -> NoticeInfo } class JSONMetricsParserFactory: MetricsParserFactory { private typealias JSONRawRepresentation = [AnyHashable: Any] private static func parseJSON(_ data: Data) throws -> JSONRawRepresentation { guard let dict = try JSONSerialization.jsonObject(with: data, options: .fragmentsAllowed) as? JSONRawRepresentation else { throw MetricsParserError.invalidFormat } return dict } func buildBuildStepParser(with type: BuildStepType) -> (Data) throws -> BuildInfo { return { data in let representation = try JSONMetricsParserFactory.parseJSON(data) guard representation["identifier"] != nil else { throw MetricsParserError.missingKey("identifier") } let startTimestamp: Double = representation.fetch("startTimestampMicroseconds") let endTimestamp: Double = representation.fetch("endTimestampMicroseconds") let detailType = DetailStepType.init(rawValue: representation.fetch("type")) ?? .other // TODO: Parse FunctionTimes as well let step = BuildStep(type: type, machineName: representation.fetch("machineName") , buildIdentifier: representation.fetch("buildIdentifier") , identifier: representation.fetch("identifier") , parentIdentifier: representation.fetch("parentIdentifier", "targetIdentifier") , domain: representation.fetch("domain") , title: representation.fetch("title", "name") , signature: representation.fetch("signature") , startDate: representation.fetch("startDate") , endDate: representation.fetch("endDate") , startTimestamp: startTimestamp, endTimestamp: endTimestamp, duration: endTimestamp - startTimestamp, detailStepType: detailType, buildStatus: representation.fetch("buildStatus"), schema: representation.fetch("schema"), subSteps: [], warningCount: representation.fetch("warningCount"), errorCount: representation.fetch("errorCount"), architecture: representation.fetch("architecture"), documentURL: representation.fetch("documentURL"), warnings: [], errors: [], notes: [], swiftFunctionTimes: nil, fetchedFromCache: representation.fetch("fetchedFromCache"), compilationEndTimestamp: representation.fetch("compilationEndTimestamp"), compilationDuration: representation.fetch("compilationDuration"), clangTimeTraceFile: nil, linkerStatistics: nil, swiftTypeCheckTimes: nil) return BuildInfo(step: step, projectName: representation.fetch("projectName"), userID: representation.fetch("userID")) } } func buildNoticeParser(defaultType: NoticeType) -> (Data) throws -> NoticeInfo { return { data in let representation = try JSONMetricsParserFactory.parseJSON(data) guard representation["buildIdentifier"] != nil else { throw MetricsParserError.missingKey("buildIdentifier") } let notice = Notice( type: NoticeType(rawValue: representation.fetch("type")) ?? defaultType, title: representation.fetch("title"), clangFlag: representation.fetch("clangFlag"), documentURL: representation.fetch("documentURL"), severity: representation.fetch("severity"), startingLineNumber: representation.fetch("startingLine"), endingLineNumber: representation.fetch("endingLine"), startingColumnNumber: representation.fetch("startingColumn"), endingColumnNumber: representation.fetch("endingColumn"), characterRangeEnd: representation.fetch("characterRangeStart"), characterRangeStart: representation.fetch("characterRangeEnd"), interfaceBuilderIdentifier: representation.fetch("interfaceBuilderIdentifier") ) let parentIdentifier: String = representation.fetch("parentIdentifier") return NoticeInfo(notice: notice, parentIdentifier: parentIdentifier) } } }